// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include "stdafx.h"
#include "Common.h"
#include "ShaderProgram.h"
#include "ShaderManager.h"
#include "Shader.h"
#include "ShaderEvent.h"


namespace
{

// Some helper functions
void printProgramInfoLog(GLuint obj);

}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

ShaderProgram::ShaderProgram() 
:   m_manager(NULL), m_id(0), m_linkValid(false)
{
    // Create an OGL program object but don't link yet because
    // we don't have any shaders
    _init();
}

ShaderProgram::~ShaderProgram()
{
    // glDeleteProgram frees the memory and invalidates the name associated 
    // with the program object specified by program. This command effectively 
    // undoes the effects of a call to glCreateProgram.
    // If a program object is in use as part of current rendering state, it 
    // will be flagged for deletion, but it will not be deleted until it is 
    // no longer part of current state for any rendering context. If a program 
    // object to be deleted has shader objects attached to it, those shader 
    // objects will be automatically detached but not deleted unless they have 
    // already been flagged for deletion by a previous call to glDeleteShader. 
    // A value of 0 for program will be silently ignored.
    glDeleteProgram(m_id);
    CHECK_GL_ERRORS();
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::_init()
{
    // glCreateProgram creates an empty program object and returns a non-zero 
    // value by which it can be referenced. A program object is an object to 
    // which shader objects can be attached. This provides a mechanism to 
    // specify the shader objects that will be linked to create a program. 
    // It also provides a means for checking the compatibility of the shaders
    // that will be used to create a program (for instance, checking the 
    // compatibility between a vertex shader and a fragment shader). When no 
    // longer needed as part of a program object, shader objects can be detached.
    // One or more executables are created in a program object by successfully 
    // attaching shader objects to it with glAttachShader, successfully 
    // compiling the shader objects with glCompileShader, and successfully 
    // linking the program object with glLinkProgram. These executables are 
    // made part of current state when glUseProgram is called. Program objects
    // can be deleted by calling glDeleteProgram. The memory associated with 
    // the program object will be deleted when it is no longer part of current 
    // rendering state for any context.
    if(m_id == 0)
    {
        m_id = glCreateProgram();
        CHECK_GL_ERRORS();
    }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::_addShader(const std::string& fn, GLenum type)
{
    std::ifstream ifs(fn.c_str());
    if(!ifs)
    {
        _error("Could not open shader file");
    }
    _addShader(ifs, type);
}

void ShaderProgram::_addShader(std::istream& ifs, GLenum type, 
                              const std::string* fn)
{
    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Create a new shader object of the appropriate type
    ref<Shader>& shader = type == GL_VERTEX_SHADER ? 
                            m_vertexShader : m_fragmentShader;
    shader.reset(new Shader(type, ifs));

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Associate the new shader object to this program
    shader->setProgram(this);

    // In order to create an executable, there must be a way to specify the 
    // list of things that will be linked together. Program objects provide 
    // this mechanism. Shaders that are to be linked together in a program 
    // object must first be attached to that program object. glAttachShader 
    // attaches the shader object specified by shader to the program object 
    // specified by program. This indicates that shader will be included in 
    // link operations that will be performed on program.
    // All operations that can be performed on a shader object are valid 
    // whether or not the shader object is attached to a program object. It 
    // is permissible to attach a shader object to a program object before 
    // source code has been loaded into the shader object or before the shader
     // object has been compiled. It is permissible to attach multiple shader 
    // objects of the same type because each may contain a portion of the 
    // complete shader. It is also permissible to attach a shader object to 
    // more than one program object. If a shader object is deleted while it 
    // is attached to a program object, it will be flagged for deletion, 
    // and deletion will not occur until glDetachShader is called to detach 
    // it from all program objects to which it is attached.
    glAttachShader(m_id, shader->getId());
    CHECK_GL_ERRORS();
    
    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Associate the new shader object to the file name from which it was read
    if(fn != NULL)
    {
        shader->setFileName(*fn);
    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Relink the program to account for the newly added shader
    invalidateLink();
    _link();

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Refresh list of uniform vars 

    m_uniformVars.clear();
    m_uniformVarVec.clear();

    // We must activate so that the uniform vars. can be read
    activate();
    std::vector<ref<UniformVar> > uniVars = getUniformVars();
    deactivate();

    for(size_t i = 0; i < uniVars.size(); ++i)
    {
        const char* name = uniVars[i]->getAnsiName();
        m_uniformVars[name] = uniVars[i];
        m_uniformVarVec.push_back(uniVars[i].get());
    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Tell manager that we have change so that it notifies its listeners
    ShaderEvent event_(type == GL_VERTEX_SHADER ? 
                                ShaderEvent::VertexShaderAdded 
                                : ShaderEvent::FragmentShaderAdded);
    event_.shader = shader.get();
    notifyEvent(event_);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::addVertexShader(std::istream& ifs)
{
    _addShader(ifs, GL_VERTEX_SHADER);
}

void ShaderProgram::addFragmentShader(std::istream& ifs)
{
    _addShader(ifs, GL_FRAGMENT_SHADER);
}

void ShaderProgram::addVertexShader(const std::string& fn)
{
    _addShader(fn, GL_VERTEX_SHADER);
}

void ShaderProgram::addFragmentShader(const std::string& fn)
{
    _addShader(fn, GL_FRAGMENT_SHADER);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::invalidateLink()
{
    m_linkValid = false;
    _link();
}

bool ShaderProgram::links() const
{
    _link();
    return m_linkValid;
}

void ShaderProgram::_link() const
{
    if(!m_linkValid)
    {

        // These are no-ops if the shaders are already compiled
        if(m_vertexShader != NULL)
        {
            m_vertexShader->compile();
        }
        if(m_fragmentShader != NULL)
        {
            m_fragmentShader->compile();
        }

        // glLinkProgram links the program object specified by program. If any
        // shader objects of type GL_VERTEX_SHADER are attached to program, 
        // they will be used to create an executable that will run on the 
        // programmable vertex processor. If any shader objects of type 
        // GL_FRAGMENT_SHADER are attached to program, they will be used to 
        // create an executable that will run on the programmable fragment 
        // processor.
        glLinkProgram(m_id);
        GLint status;
        glGetProgramiv(m_id, GL_LINK_STATUS, &status);
        m_linkValid = status == GL_TRUE;

#if MPG_VERBOSE_SHADERS
    printProgramInfoLog(m_id);
#endif

    }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::activate()
{
    ASSERT(m_linkValid);

    // glUseProgram installs the program object specified by program as part 
    // of current rendering state. One or more executables are created in a 
    // program object by successfully attaching shader objects to it with 
    // glAttachShader, successfully compiling the shader objects with 
    // glCompileShader, and successfully linking the program object with 
    // glLinkProgram.    
    glUseProgram(m_id);
    CHECK_GL_ERRORS();
}

bool ShaderProgram::_isActive() const
{
    GLint curr;
    glGetIntegerv(GL_CURRENT_PROGRAM, &curr);
    CHECK_GL_ERRORS();

    return curr == (GLint)m_id;
}

void ShaderProgram::deactivate()
{
    if(!_isActive())
    {
        _error("Cannot deactivate inactive shader program");
    }

    glUseProgram(0);
    CHECK_GL_ERRORS();
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

Shader* ShaderProgram::getVertexShader() const
{
    return m_vertexShader.get();
}

Shader* ShaderProgram::getFragmentShader() const
{
    return m_fragmentShader.get();
}



// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::_error(const char* msg) const
{
    std::cerr << "SHADER ERROR: " << msg << std::endl;
    throw ShaderProgramException(msg);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void ShaderProgram::setUniformVar(const std::string& name, 
                                  const UniformVarParams& var)
{
    // Make sure the program can be linked correctly before setting any
    // uniform vars
    ASSERT(m_linkValid);

    UniformVarMap::iterator it = m_uniformVars.find(name);
    ASSERT(it != m_uniformVars.end());

    it->second->setValue(var);

    ShaderEvent event_(ShaderEvent::UniformVariableChanged);
    event_.uniVar = it->second.get();
    notifyEvent(event_);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


void ShaderProgram::notifyEvent(ShaderEvent& event_)
{
    if(m_manager != NULL)
    {
        event_.program = this;
        m_manager->notifyShaderEvent(event_);
    }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


bool ShaderProgram::hasVertexShader() const
{
    return m_vertexShader != NULL;
}

bool ShaderProgram::hasFragmentShader() const
{
    return m_fragmentShader != NULL;
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

size_t ShaderProgram::getNumUniformVars() const
{
    return m_uniformVars.size();
}

UniformVar* ShaderProgram::getUniformVar(int i) const
{
    return m_uniformVarVec[i];
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

std::vector<ref<UniformVar> > ShaderProgram::getUniformVars()
{
    std::vector<ref<UniformVar> > result;

    // glGetActiveUniform returns information about an active uniform variable in the program object specified by program. 
    // The number of active uniform variables can be obtained by calling glGetProgram with the value GL_ACTIVE_UNIFORMS. 
    // A value of 0 for index selects the first active uniform variable. Permissible values for index range from 0 to the 
    // number of active uniform variables minus 1.

    GLint numActiveUniforms;
    GLint maxLength;
    glGetProgramiv(
        m_id,
        GL_ACTIVE_UNIFORMS, // Returns the number of active uniform variables for program.
        &numActiveUniforms);
    dbg(numActiveUniforms);
    
    glGetProgramiv(
        m_id, 
        GL_ACTIVE_UNIFORM_MAX_LENGTH, // Returns the length of the longest active uniform variable 
        &maxLength                    // name for program, including the null termination character (i.e., the variable name). 
    );                                // If no active uniform variables exist, 0 is returned.
                                      // size of the character buffer required to store the longest uniform 

    // Shaders may use either built-in uniform variables, user-defined uniform variables, or both. Built-in uniform variables 
    // have a prefix of "gl_" and reference existing OpenGL state or values derived from such state (e.g., gl_Fog, 
    // gl_ModelViewMatrix, etc., see the OpenGL Shading Language specification for a complete list.) User-defined uniform 
    // variables have arbitrary names and obtain their values from the application through calls to glUniform. A uniform 
    // variable (either built-in or user-defined) is considered active if it is determined during the link operation that 
    // it may be accessed during program execution. Therefore, program should have previously been the target of a call 
    // to glLinkProgram, but it is not necessary for it to have been linked successfully.

    // The list of active uniform variables may include both built-in uniform variables (which begin with the prefix "gl_") 
    // as well as user-defined uniform variable names.

    for(int i = 0; i < numActiveUniforms; ++i)
    {
        GLsizei nameLen;
        GLsizei size;
        GLenum type;
        std::string name;
        name.resize(maxLength);
        glGetActiveUniform(
            m_id,   // Specifies the program object to be queried.
            i,      // Specifies the index of the uniform variable to be queried.
            maxLength, // Specifies the maximum number of characters OpenGL is allowed to write in the character buffer indicated by name.
            &nameLen, // Returns the number of characters actually written by OpenGL in the string 
                    // indicated by name (excluding the null terminator) if a value other than NULL is passed.
            &size, // Returns the size of the uniform variable. Uniform variables other than arrays will have a size of 1
            &type, // Returns the data type of the uniform variable.
            &name[0] // Returns a null terminated string containing the name of the uniform variable.
        );
        name.resize(nameLen);
        CHECK_GL_ERRORS();

        // glGetUniformLocation returns -1 if name does not correspond to an active uniform variable 
        // in program or if name starts with the reserved prefix "gl_".
        if(name.find("gl_") == 0)
        {
            continue;
        }

        UniformVar var(type);
        var.setProgram(this);
        var.setAnsiName(name);
        var.readFromRenderContext();
        CHECK_GL_ERRORS();

        result.push_back(ref<UniformVar>(new UniformVar(var)));
    }

    return result;
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

namespace
{

void printProgramInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
        dbg(infoLog);
        free(infoLog);
    }
}

}

